#ifndef GPS_NMEA_H
#define GPS_NMEA_H

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <fstream>
#include <chrono>
#include <algorithm>
#include <unistd.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <string.h>
#include <map>
#include <mutex>
#include <vector>
#include <string>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/statfs.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <math.h>
#include <thread>
#include <sys/types.h>
#include <dirent.h>



// used to pack structures
#define PACKED __attribute__((__packed__))


/// GPS status codes
enum GPS_Status {
	NO_GPS = 0, //GPS_FIX_TYPE_NO_GPS,                     ///< No GPS connected/detected
	NO_FIX = 1, //GPS_FIX_TYPE_NO_FIX,                     ///< Receiving valid GPS messages but no lock
	GPS_OK_FIX_2D = 2,//GPS_FIX_TYPE_2D_FIX,              ///< Receiving valid messages and 2D lock
	GPS_OK_FIX_3D = 3, //GPS_FIX_TYPE_3D_FIX,              ///< Receiving valid messages and 3D lock
	GPS_OK_FIX_3D_DGPS = 4, //GPS_FIX_TYPE_DGPS,           ///< Receiving valid messages and 3D lock with differential improvements
	GPS_OK_FIX_3D_RTK_FLOAT = 5, //GPS_FIX_TYPE_RTK_FLOAT, ///< Receiving valid messages and 3D RTK Float
	GPS_OK_FIX_3D_RTK_FIXED = 6, //GPS_FIX_TYPE_RTK_FIXED, ///< Receiving valid messages and 3D RTK Fixed
};

typedef struct Vector3f {
    float x;
    float y;
    float z;
} Vector3f;



////////////////////////////////////////////////////////////////////////////////
/// @name	Types
///
/// Data structures and types used throughout the libraries and applications. 0 = default
/// bit 0: Altitude is stored               0: Absolute,	1: Relative
/// bit 1: Change Alt between WP            0: Gradually,	1: ASAP
/// bit 2: Direction of loiter command      0: Clockwise	1: Counter-Clockwise
/// bit 3: Req.to hit WP.alt to continue    0: No,          1: Yes
/// bit 4: Relative to Home					0: No,          1: Yes
/// bit 5: Loiter crosstrack reference      0: WP center    1: Tangent exit point
/// bit 6:
/// bit 7: Move to next Command             0: YES,         1: Loiter until commanded

//@{

struct PACKED Location_Option_Flags {
    uint8_t relative_alt : 1;           // 1 if altitude is relateive to home
    uint8_t unused1      : 1;           // unused flag (defined so that loiter_ccw uses the correct bit)
    uint8_t loiter_ccw   : 1;           // 0 if clockwise, 1 if counter clockwise
    uint8_t terrain_alt  : 1;           // this altitude is above terrain
    uint8_t origin_alt   : 1;           // this altitude is above ekf origin
    uint8_t loiter_xtrack : 1;          // 0 to crosstrack from center of waypoint, 1 to crosstrack from tangent exit location
};


struct PACKED Location {
    union {
        Location_Option_Flags flags;                    ///< options bitmask (1<<0 = relative altitude)
        uint8_t options;                                /// allows writing all flags to eeprom as one byte
    };
    // by making alt 24 bit we can make p1 in a command 16 bit,
    // allowing an accurate angle in centi-degrees. This keeps the
    // storage cost per mission item at 15 bytes, and allows mission
    // altitudes of up to +/- 83km
    int32_t alt:24;                                     ///< param 2 - Altitude in centimeters (meters * 100) see LOCATION_ALT_MAX_M
    int32_t lat;                                        ///< param 3 - Latitude * 10**7
    int32_t lng;                                        ///< param 4 - Longitude * 10**7
};

/*
      The GPS_State structure is filled in by the backend driver as it
      parses each message from the GPS.
*/
struct GPS_State {
	uint8_t instance; // the instance number of this GPS

	// all the following fields must all be filled by the backend driver
	GPS_Status status;                  ///< driver fix status
	uint32_t time_week_ms;              ///< GPS time (milliseconds from start of GPS week)
	uint16_t time_week;                 ///< GPS week number
	Location location;                  ///< last fix location
	float ground_speed;                 ///< ground speed in m/sec
	float ground_course;                ///< ground course in degrees
	uint16_t hdop;                      ///< horizontal dilution of precision in cm
	uint16_t vdop;                      ///< vertical dilution of precision in cm
	uint8_t num_sats;                   ///< Number of visible satellites
	Vector3f velocity;                  ///< 3D velocitiy in m/s, in NED format
	float speed_accuracy;
	float horizontal_accuracy;
	float vertical_accuracy;
	bool have_vertical_velocity:1;      ///< does this GPS give vertical velocity?
	bool have_speed_accuracy:1;
	bool have_horizontal_accuracy:1;
	bool have_vertical_accuracy:1;
	uint32_t last_gps_time_ms;          ///< the system time we got the last GPS timestamp, milliseconds
};

class GpsNmea
{
public:
	GpsNmea();
	~GpsNmea();

	/// Checks the serial receive buffer for characters,
    /// attempts to parse NMEA data and updates internal state
    /// accordingly.
    bool        gps_read();

	bool 		gps_init();

	GPS_State state;


private:
	/// Coding for the GPS sentences that the parser handles
    enum _sentence_types {      //there are some more than 10 fields in some sentences , thus we have to increase these value.
        _GPS_SENTENCE_RMC = 32,
        _GPS_SENTENCE_GGA = 64,
        _GPS_SENTENCE_VTG = 96,
        _GPS_SENTENCE_OTHER = 0
    };


    /// Update the decode state machine with a new character
    ///
    /// @param	c		The next character in the NMEA input stream
    /// @returns		True if processing the character has resulted in
    ///					an update to the GPS state
    ///
    bool                        _decode(char c);

    /// Return the numeric value of an ascii hex character
    ///
    /// @param	a		The character to be converted
    /// @returns		The value of the character as a hex digit
    ///
    int16_t                     _from_hex(char a);

    /// Parses the @p as a NMEA-style decimal number with
    /// up to 3 decimal digits.
    ///
    /// @returns		The value expressed by the string in @p,
    ///					multiplied by 100.
    ///
    static int32_t _parse_decimal_100(const char *p);

    /// Parses the current term as a NMEA-style degrees + minutes
    /// value with up to four decimal digits.
    ///
    /// This gives a theoretical resolution limit of around 1cm.
    ///
    /// @returns		The value expressed by the string in _term,
    ///					multiplied by 1e7.
    ///
    uint32_t    _parse_degrees();

    /// Processes the current term when it has been deemed to be
    /// complete.
    ///
    /// Each GPS message is broken up into terms separated by commas.
    /// Each term is then processed by this function as it is received.
    ///
    /// @returns		True if completing the term has resulted in
    ///					an update to the GPS state.
    bool                        _term_complete();

    /// return true if we have a new set of NMEA messages
    bool _have_new_message(void);


	/*
       fill in time_week_ms and time_week from BCD date and time components
       assumes MTK19 millisecond form of bcd_time
    */
    void make_gps_time(uint32_t bcd_date, uint32_t bcd_milliseconds);

	/*
      fill in 3D velocity from 2D components
     */
    void fill_3d_velocity(void);

	int 	ttyConfigure(char *ttyPort, char *baudrate);

    uint8_t _parity;                                                    ///< NMEA message checksum accumulator
    bool _is_checksum_term;                                     ///< current term is the checksum
    char _term[15];                                                     ///< buffer for the current term within the current sentence
    uint8_t _sentence_type;                                     ///< the sentence type currently being processed
    uint8_t _term_number;                                       ///< term index within the current sentence
    uint8_t _term_offset;                                       ///< character offset with the term being received
    bool _gps_data_good;                                        ///< set when the sentence indicates data is good

    // The result of parsing terms within a message is stored temporarily until
    // the message is completely processed and the checksum validated.
    // This avoids the need to buffer the entire message.
    int32_t _new_time;                                                  ///< time parsed from a term
    int32_t _new_date;                                                  ///< date parsed from a term
    int32_t _new_latitude;                                      ///< latitude parsed from a term
    int32_t _new_longitude;                                     ///< longitude parsed from a term
    int32_t _new_altitude;                                      ///< altitude parsed from a term
    int32_t _new_speed;                                                 ///< speed parsed from a term
    int32_t _new_course;                                        ///< course parsed from a term
    uint16_t _new_hdop;                                                 ///< HDOP parsed from a term
    uint8_t _new_satellite_count;                       ///< satellite count parsed from a term

    uint32_t _last_RMC_ms = 0;
    uint32_t _last_GGA_ms = 0;
    uint32_t _last_VTG_ms = 0;

	int 			m_ttyFd;

    /// @name	Init strings
    ///			In ::init, an attempt is made to configure the GPS
    ///			unit to send just the messages that we are interested
    ///			in using these strings
    //@{
    static const char _SiRF_init_string[];         ///< init string for SiRF units
    static const char _MTK_init_string[];                  ///< init string for MediaTek units
    static const char _ublox_init_string[];        ///< init string for ublox units
    //@}

    static const char _initialisation_blob[];

};

extern GpsNmea GpsNmeaHandler;

#endif //GPS_NMEA_H
